package com.hulk.dryad.manage.s3.template;

import com.hulk.dryad.manage.config.S3FsClientConfig;
import com.hulk.dryad.manage.s3.S3FsProperties;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.Exceptions;
import net.dreamlu.mica.core.utils.JsonUtil;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.sts.StsAsyncClient;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.Credentials;
import software.amazon.awssdk.services.sts.model.GetFederationTokenRequest;
import software.amazon.awssdk.utils.StringUtils;

import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 *  文件系统实现
 *
 * @author hulk
 */
@Configuration
@ConditionalOnBean(S3FsClientConfig.class)
@Slf4j
@Getter
@RequiredArgsConstructor
public class FsTemplateImpl implements FsTemplate {
	private final StsClient stsClient;
	private final S3Client s3Client;
	private final StsAsyncClient stsAsyncClient;
	private final S3AsyncClient s3AsyncClient;
	private final S3FsProperties fsProperties;

	@Override
	public Credentials getFederationToken(int timeOut) {
		return getFederationToken(fsProperties.getBucket(), timeOut);
	}

	@Override
	public Credentials getFederationToken(String bucket, int timeOut) {
		Map<String, Object> policy = new HashMap<>(2);
		policy.put("Version", "2012-10-17");
		Map<String, Object> statement = new HashMap<>(3);
		statement.put("Effect", "Allow");
		// 授权零时票据上传文件权限
		statement.put("Action", "s3:PutObject");
		// "Resource": "arn:aws:s3:::my_corporate_bucket/*"
		statement.put("Resource", String.format("arn:aws:s3:::%s/*", bucket));
		policy.put("Statement", statement);
		// 获取 sts token
		GetFederationTokenRequest.Builder builder = GetFederationTokenRequest.builder()
			.name("FederationToken")
			.policy(JsonUtil.toJson(policy));
		// 超时时间
		if (timeOut > 0) {
			builder.durationSeconds(timeOut);
		}
		return getFederationToken(builder.build());
	}

	@Override
	public Credentials getFederationToken(GetFederationTokenRequest federationTokenRequest) {
		return stsClient.getFederationToken(federationTokenRequest).credentials();
	}

	@Override
	public Credentials getSessionToken() {
		return stsClient.getSessionToken().credentials();
	}

	@Override
	public HeadBucketResponse headBucket(String bucket) {
		HeadBucketResponse response = s3Client.headBucket(builder -> builder.bucket(bucket));
		log.info("s3 bucket head success");
		return response;
	}

	@Override
	public boolean createBucket(Consumer<CreateBucketRequest.Builder> createBucketRequest) {
		try {
			s3Client.createBucket(createBucketRequest);
			log.info("s3 create bucket success");
			return true;
		} catch (Throwable e) {
			log.warn("S3 create bucket fail， error message：{}", e.getMessage());
			return false;
		}
	}

	@Override
	public boolean deleteBucket(String bucketName) {
		try {
			s3Client.deleteBucket(builder -> builder.bucket(bucketName));
			log.info("s3 delete bucket success");
			return true;
		} catch (Throwable e) {
			log.warn("S3 delete bucket fail， error message：{}", e.getMessage());
			return false;
		}
	}

	@Override
	public boolean isBucketExist() {
		return isBucketExist(fsProperties.getBucket());
	}

	@Override
	public boolean isBucketExist(String bucket) {
		try {
			headBucket(bucket);
			return true;
		} catch (Throwable e) {
			log.warn("S3 bucket not exist， error message：{}", e.getMessage());
			return false;
		}
	}

	@Override
	public HeadObjectResponse head(Consumer<HeadObjectRequest.Builder> headObjectRequest) {
		return s3Client.headObject(headObjectRequest);
	}

	@Override
	public ResponseInputStream<GetObjectResponse> get(GetObjectRequest getObjectRequest) {
		return s3Client.getObject(getObjectRequest);
	}

	@Override
	public void copy(Consumer<CopyObjectRequest.Builder> copyObjectRequest) {
		s3Client.copyObject(copyObjectRequest);
	}

	@Override
	public List<String> list(String prefix, int size, String lastKey) {
		return list(fsProperties.getBucket(), prefix, size, lastKey);
	}

	@Override
	public List<String> list(String bucket, String prefix, int size, String lastKey) {
		// 创建拉取请求
		ListObjectsV2Request.Builder builder = ListObjectsV2Request.builder()
			.maxKeys(size)
			.bucket(bucket);
		// 前缀
		if (StringUtils.isNotBlank(prefix)) {
			builder.prefix(prefix);
		}
		// 开始的 key
		if (StringUtils.isNotBlank(lastKey)) {
			builder.startAfter(lastKey);
		}
		try {
			ListObjectsV2Response listObjects = s3Client.listObjectsV2(builder.build());
			log.info("s3 file list success.");
			return listObjects.contents().stream()
				.map(S3Object::key)
				.collect(Collectors.toList());
		} catch (Throwable e) {
			log.error("s3 file list success fail.", e);
			return Collections.emptyList();
		}
	}

	@Override
	public PutObjectResponse upload(String key, InputStream in, long contentLength) {
		return upload(fsProperties.getBucket(), key, in, contentLength);
	}

	@Override
	public PutObjectResponse upload(String bucket, String key, InputStream in, long contentLength) {
		PutObjectRequest putObjectRequest = PutObjectRequest.builder().bucket(fsProperties.getBucket()).key(key).build();
		RequestBody requestBody = RequestBody.fromInputStream(in, contentLength);
		try {
			PutObjectResponse putObjectResponse = s3Client.putObject(putObjectRequest, requestBody);
			log.info("s3 file upload success.");
			return putObjectResponse;
		} catch (AwsServiceException | SdkClientException e) {
			throw Exceptions.unchecked(e);
		}
	}

	@Override
	public UploadPartResponse uploadPart(UploadPartRequest uploadPartRequest, RequestBody requestBody) {
		try {
			UploadPartResponse uploadPartResponse = s3Client.uploadPart(uploadPartRequest, requestBody);
			log.info("s3 file upload part success.");
			return uploadPartResponse;
		} catch (AwsServiceException | SdkClientException e) {
			throw Exceptions.unchecked(e);
		}
	}

	@Override
	public String createMultipartUpload(String currentObject) {
		return createMultipartUpload(fsProperties.getBucket(), currentObject);
	}

	@Override
	public String createMultipartUpload(String bucket, String currentObject) {
		CreateMultipartUploadRequest uploadRequest = CreateMultipartUploadRequest.builder()
			.bucket(bucket)
			.key(currentObject)
			.build();
		try {
			CreateMultipartUploadResponse multipartUpload = s3Client.createMultipartUpload(uploadRequest);
			log.info("s3 file create upload multi part success.");
			return multipartUpload.uploadId();
		} catch (AwsServiceException | SdkClientException e) {
			throw Exceptions.unchecked(e);
		}
	}

	@Override
	public CompleteMultipartUploadResponse completeMultipartUpload(String currentObject, String uploadId, List<CompletedPart> eTagList) {
		return completeMultipartUpload(fsProperties.getBucket(), currentObject, uploadId, eTagList);
	}

	@Override
	public CompleteMultipartUploadResponse completeMultipartUpload(String bucket, String currentObject, String uploadId, List<CompletedPart> eTagList) {
		CompletedMultipartUpload multipartUpload = CompletedMultipartUpload.builder()
			.parts(eTagList)
			.build();
		CompleteMultipartUploadRequest request = CompleteMultipartUploadRequest.builder()
			.bucket(fsProperties.getBucket())
			.key(currentObject)
			.uploadId(uploadId)
			.multipartUpload(multipartUpload)
			.build();
		try {
			CompleteMultipartUploadResponse response = s3Client.completeMultipartUpload(request);
			log.info("s3 file complete upload multi part success.");
			return response;
		} catch (AwsServiceException | SdkClientException e) {
			throw Exceptions.unchecked(e);
		}
	}

	@Override
	public List<DeletedObject> delete(List<String> keys) {
		return delete(fsProperties.getBucket(), keys);
	}

	@Override
	public List<DeletedObject> delete(String bucket, List<String> keys) {
		if (keys == null || keys.isEmpty()) {
			return Collections.emptyList();
		}
		// 构造删除对象列表
		List<ObjectIdentifier> objectIdentifierList = keys.stream()
			.map(key -> ObjectIdentifier.builder().key(key).build())
			.collect(Collectors.toList());
		// 构造删除请
		DeleteObjectsRequest deleteObjectsRequest = DeleteObjectsRequest.builder()
			.bucket(bucket)
			.delete(builder -> builder.objects(objectIdentifierList))
			.build();
		try {
			DeleteObjectsResponse response = s3Client.deleteObjects(deleteObjectsRequest);
			log.info("s3 file delete success.");
			return response.deleted();
		} catch (AwsServiceException | SdkClientException e) {
			throw Exceptions.unchecked(e);
		}
	}

}
